import type { DataTableBaseColumn, DataTableColumn, DataTableExpandColumn, DataTableSelectionColumn } from 'naive-ui'
import type { Ref, VNodeChild } from 'vue'
import type { NvCrudFieldOption, NvCrudProps, NvTargetStorage } from '@/crud/types'
import type { UseActionsReturn } from '@/crud/use-actions'
import type { Constructor, Size } from '@/types'
import type { DictComponentSlotParams } from '@/utils/use-dict'
import { Delete24Regular, Edit24Regular, Eye24Regular } from '@vicons/fluent'
import { get, pick } from 'lodash-es'
import { NCheckbox, NFlex } from 'naive-ui'
import { computed, customRef, inject, provide, unref } from 'vue'
import { NvButton } from '@/components'
import NvDict from '@/components/NvDict.vue'
import { NV_CRUD_CONTEXT } from '@/context'
import { resolveBind, resolveVisible, sortCompare } from '@/utils'
import { useLocale } from '@/utils/locale'

/** fields 配置中表格列的属性 */
export const NV_TABLE_COLUMN_KEYS = [
  'title', 'titleColSpan', 'titleAlign', 'fixed', 'align', 'width', 'minWidth', 'maxWidth', 'className', 'render',
  'rowSpan', 'colSpan', 'children', 'resizable', 'tree', 'ellipsis', 'ellipsisComponent', 'allowExport', 'cellProps',
  'sorter', 'defaultSortOrder', 'sortOrder', 'renderSorter', 'renderSorterIcon',
  'filter', 'filterOptions', 'filterOptionValues', 'filterOptionValue', 'filterMode', 'filterMultiple',
  'defaultFilterOptionValues', 'defaultFilterOptionValue', 'renderFilter', 'renderFilterIcon', 'renderFilterMenu',
] as const

export function useColumns(
  props: NvCrudProps,
  slots: Record<string, Function>,
  size: Ref<Size>,
  actions: Pick<UseActionsReturn, 'rowView' | 'rowEdit' | 'rowDel'>,
) {
  const t = useLocale()

  const selectionColumn = computed<DataTableSelectionColumn | null>(() => {
    const selectionOption = props.option.selection
    if (!selectionOption)
      return null
    return {
      key: '_nv_row_selection',
      title: () => (
        <NCheckbox
          checked
          disabled
          style={{
            'cursor': 'unset',
            '--n-border-checked': 'var(--n-border-disabled-checked)',
            '--n-box-shadow-focus': 'none',
          }}
        />
      ),
      type: 'selection',
      fixed: 'left',
      ...selectionOption,
    }
  })

  const expansionColumn = computed<DataTableExpandColumn | null>(() => {
    const expansionOption = props.option.expansion
    if (!expansionOption)
      return null
    return {
      key: '_nv_row_expansion',
      type: 'expand',
      fixed: 'left',
      ...expansionOption,
    }
  })

  const serialNoColumn = computed<DataTableBaseColumn | null>(() => {
    const serialNoOption = props.option.serialNo
    if (!serialNoOption) {
      return null
    }
    return {
      key: '_nv_row_serial_num',
      title: '#',
      fixed: 'left',
      width: 60,
      render: (_, rowIndex) => {
        if (props.pagination) {
          const { page, pageSize } = props.pagination
          if (typeof page === 'number' && typeof pageSize === 'number') {
            return (page - 1) * pageSize + rowIndex + 1
          }
        }
        return rowIndex + 1
      },
      ...serialNoOption,
    }
  })

  const buildColumnRender = (field: string, fieldOption: NvCrudFieldOption) => {
    let renderSlot: (props: DictComponentSlotParams) => VNodeChild

    // 插槽
    const slot = slots[`${field}_view`]
    if (slot) {
      renderSlot = (params) => {
        return slot({ mode: 'cell', ...params })
      }
    }
    // 自定义渲染
    else if (fieldOption.render) {
      renderSlot = (params) => {
        return fieldOption.render!(params)
      }
    }
    // 默认组件
    else {
      const { config } = NV_CRUD_CONTEXT.getComponent(fieldOption.type || 'input', 'input')
      const renderView = config?.renderView
      if (!renderView)
        return undefined

      const vModel = config?.vModel || 'value'

      renderSlot = ({ rowData, ...rest }) => {
        const props = { ...fieldOption, ...fieldOption.props, mode: 'cell', [vModel]: get(rowData, field), ...rest }
        if (typeof renderView === 'function') {
          const renderViewFunc = renderView as Function
          return renderViewFunc(props)
        }
        else {
          const ViewComponent = renderView as Constructor
          return (
            <ViewComponent {...props}>
              {{ ...fieldOption.slots }}
            </ViewComponent>
          )
        }
      }
    }
    const render: DataTableBaseColumn['render'] = (rowData, rowIndex) => {
      const option = { ...fieldOption }
      if (option.cellDict) {
        option.dict = option.cellDict
      }
      return (
        <NvDict form={rowData} index={rowIndex} field={field} option={option}>
          {{ default: renderSlot }}
        </NvDict>
      )
    }
    return render
  }

  const fieldColumns = computed<DataTableColumn[]>(() => {
    function resolveFields(fields: Record<string, NvCrudFieldOption>): DataTableColumn[] {
      return Object.entries(fields)
        .filter(([_, fieldOption]) => unref(fieldOption.visible) !== false)
        .sort(([, a], [, b]) => sortCompare(a?.order, b?.order))
        .map<DataTableBaseColumn>(([field, fieldOption]) => {
          const { children, ...columnObj } = pick(fieldOption, NV_TABLE_COLUMN_KEYS)
          const column: DataTableColumn = {
            ...columnObj,
            title: fieldOption.title ?? fieldOption.label,
            key: field,
            render: buildColumnRender(field, fieldOption),
          }
          if (children) {
            return {
              ...column,
              children: resolveFields(children),
            }
          }

          return column
        })
    }

    return resolveFields(props.option.fields)
  })

  const actionsColumn = computed<DataTableBaseColumn | null>(() => {
    const _rowActions = props.option?.rowActions

    if (_rowActions === false)
      return null
    const _actions = typeof _rowActions === 'object' ? _rowActions : undefined

    return {
      key: '_nv_row_actions',
      title: _actions?.title ?? t.value.table.actions,
      fixed: _actions?.fixed ?? 'right',
      width: _actions?.width ?? 220,
      minWidth: _actions?.minWidth,
      maxWidth: _actions?.maxWidth,
      className: _actions?.className,
      titleAlign: _actions?.titleAlign,
      align: _actions?.align,
      render(rowData: object, rowIndex: number) {
        const viewIcon = () => <Eye24Regular />
        const editIcon = () => <Edit24Regular />
        const delIcon = () => <Delete24Regular />
        return (
          <NFlex align="center" justify={_actions?.align} class="nv-row-actions">
            { slots.row_actions_pre?.({ rowData, rowIndex, size: size.value })}
            { resolveVisible(_actions?.viewBtn, true) && (
              slots.row_actions_view ? (
                slots.row_actions_view({ rowData, rowIndex, size: size.value, icon: viewIcon })
              ) : (
                <NvButton
                  text
                  type="primary"
                  content={t.value.button.view}
                  size={size.value}
                  {...resolveBind(_actions?.viewBtn)}
                  defaultRenderIcon={viewIcon}
                  onClick={() => actions.rowView(rowData)}
                />
              )
            ) }
            { resolveVisible(_actions?.editBtn, true) && (
              slots.row_actions_edit ? (
                slots.row_actions_edit({ rowData, rowIndex, size: size.value, icon: editIcon })
              ) : (
                <NvButton
                  text
                  type="primary"
                  content={t.value.button.edit}
                  size={size.value}
                  {...resolveBind(_actions?.editBtn)}
                  defaultRenderIcon={editIcon}
                  onClick={() => actions.rowEdit(rowData)}
                />
              )
            ) }
            { resolveVisible(_actions?.delBtn, true) && (
              slots.row_actions_del ? (
                slots.row_actions_del({ rowData, rowIndex, size: size.value, icon: delIcon })
              ) : (
                <NvButton
                  text
                  type="primary"
                  content={t.value.button.del}
                  size={size.value}
                  {...resolveBind(_actions?.delBtn)}
                  defaultRenderIcon={delIcon}
                  onClick={() => actions.rowDel(rowData)}
                />
              )
            ) }
            { slots.row_actions?.({ rowData, rowIndex, size: size.value })}
          </NFlex>
        )
      },
    }
  })

  const columns = computed<DataTableColumn[]>(() => {
    const _columns: DataTableColumn[] = [...fieldColumns.value]
    if (serialNoColumn.value) {
      _columns.unshift(serialNoColumn.value)
    }
    if (expansionColumn.value) {
      _columns.unshift(expansionColumn.value)
    }
    if (selectionColumn.value) {
      _columns.unshift(selectionColumn.value)
    }
    if (actionsColumn.value) {
      _columns.push(actionsColumn.value)
    }
    return _columns
  })

  const { draggableOrderColumns } = useDraggableOrderColumns(props, columns)

  const dataTableColumns = computed<DataTableColumn[]>(() => {
    console.debug('computed dataTableColumns')
    return draggableOrderColumns.value
      .filter(({ visible }) => visible !== false)
      .map((column) => {
        const _column = columns.value.find(c => (c as any).key === column.key)
        if (_column) {
          return {
            ..._column,
            fixed: column.fixed,
          }
        }
        return null
      })
      .filter(Boolean) as DataTableColumn[]
  })

  return { dataTableColumns }
}

export interface DraggableOrderColumn {
  key: string
  title: NvCrudFieldOption['title']
  visible: boolean
  fixed?: DataTableColumn['fixed'] | null
}
const key = Symbol('order-columns')
function useDraggableOrderColumns(props: NvCrudProps, columns: Ref<DataTableColumn[]>) {
  const DRAGGABLE_KEY = `nv_crud_id_${props.option.id}`

  let STORAGE: NvTargetStorage = localStorage
  if (props.option.targetStorage) {
    STORAGE = props.option.targetStorage
  }

  const draggableOrderColumns = customRef<DraggableOrderColumn[]>((track, trigger) => {
    let _value: DraggableOrderColumn[] | null = null
    return {
      get() {
        track()
        if (_value) {
          return _value
        }
        return columns.value.map((column) => {
          const _column = column as any
          return {
            key: _column.key,
            title: _column.title,
            fixed: _column.fixed,
            visible: true,
          }
        })
      },
      set(value) {
        if (props.option.id) {
          if (value) {
            STORAGE.setItem(DRAGGABLE_KEY, JSON.stringify(value))
          }
          else {
            STORAGE.removeItem(DRAGGABLE_KEY)
          }
        }
        _value = value
        trigger()
      },
    }
  })

  async function initDraggableOrderColumns() {
    try {
      const _draggableJson = await STORAGE.getItem(DRAGGABLE_KEY)
      if (_draggableJson) {
        draggableOrderColumns.value = JSON.parse(_draggableJson).map((column: DraggableOrderColumn) => ({
          ...column,
          // eslint-disable-next-line ts/ban-ts-comment
          // @ts-ignore
          title: columns.value.find(item => item.key === column.key)?.title ?? 'Unknown',
        }))
      }
    }
    catch (e) {
      console.error(e)
      STORAGE.removeItem(DRAGGABLE_KEY)
    }
  }
  initDraggableOrderColumns()

  provide(key, draggableOrderColumns)

  return { draggableOrderColumns }
}

export function useOrderColumns() {
  return inject(key) as Ref<DraggableOrderColumn[] | null>
}
